package settings;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;


/**
 * This class is able to load and store the settings represented in a XML-Tree.
 * 
 * @author daishy
 * @date 23.03.2008 16:36:59
 *
 */
public class Settings 
{
	/**
	 * This is the only instance of this class
	 */
	private static Settings instance;
	
	/**
	 * This saves the XML-Document to which all changes made in the settings
	 * classes will apply. The save method will save this document.
	 */
	private static Document document;
	
	/**
	 * creating a new instance and loading 
	 * the settings file. If the file is not available
	 * @param file
	 */
	public static void init(File file) throws IOException, JDOMException
	{		
		if(file == null || !file.isFile())
			throw new IllegalArgumentException("File is not valid");
		
		// reading the document
		SAXBuilder builder = new SAXBuilder();
		
		Settings.document = builder.build(file);
		
		// creating the root element
		if(document.getRootElement() == null)
		{
			Element elem = new Element("settings");
			document.addContent(elem);
			instance = new Settings(elem);
		}
		else
		{
			instance = new Settings(document.getRootElement());
		}
	}
	
	/**
	 * Saves the settingsfile to the specified file. The file will be overwritten if
	 * it already exists
	 * 
	 * @param file
	 * @throws IOException
	 */
	public static void saveSettings(java.io.File file) throws IOException
	{
		// checking for valid arguments and state
		if(file == null || !file.isFile())
			throw new IllegalArgumentException("Settings.readSettings");
		if(Settings.document == null)
			throw new IllegalStateException("No Document available");
		
		// Writing the document to the file
		XMLOutputter output = new XMLOutputter();
		BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
		
		output.output(Settings.document, stream);		
	}
	
	/**
	 * This method returns a reference to the stored
	 * instance containing the settings
	 * @return
	 */
	public static Settings getInstance()
	{
		if(instance == null)
			throw new IllegalStateException("Settings not loaded yet.");
		
		return instance;
	}
	
	/**
	 * The XML-Element which represents this settings-level
	 */
	private Element element;
	
	/**
	 * CTor is private, cause the only method which should create settings
	 * directly is the loadSettings method of this class.
	 */
	private Settings(Element elem)
	{
		if(elem == null)
			throw new IllegalArgumentException("No null-values allowed here.");
		this.element = elem;
	}
	
	/**
	 * This method will search the top level of the XML-document
	 * for the specified name. If the name is found, the xml-object is
	 * wrapped in a settingsobject and returned.
	 * If it is was not found a new entry in the document will be created
	 * and returned
	 * @param name The tag to search for
	 * @return a Settingsobject of the hirachy
	 */
	public Settings getTree(String name)
	{
		// if the child was not found
		if(!hasChild(name))
		{
			Element elem = new Element(name);
			getElement().addContent(elem);
			return new Settings(elem);
		}
		else
		{
			Element elem = getElement().getChild(name);
			return new Settings(elem); 
		}
	}
	
	/**
	 * This will return the value of the specified tag
	 * or null if the child is not available
	 * @param name
	 * @return
	 */
	public String getValue(String name)
	{
		if(!hasChild(name))
		{
			return null;			
		}
		else
		{
			return this.element.getChildText(name);
		}
		
	}
	
	/**
	 * This will set the specified tag with the value.
	 * If the tag exists it will be overwritten, if it doesnt exist
	 * it will be created
	 * @param name The tag to set
	 * @param value The value to set
	 */
	public void setValue(String name, String value)
	{	
		if(hasChild(name))
		{
			this.element.getChild(name).setText(value);			
		}
		else
		{
			Element elem = new Element(name);
			elem.setText(value);
			this.element.addContent(elem);	
						
		}
		
	}
	
	/**
	 * This will add the specified tag with the specified value,
	 * but only if the tag does not exist
	 * @param name
	 * @param value
	 */
	public void addDefaultValue(String name, String value)
	{
		if(hasChild(name))
			return;
		setValue(name, value);
	}
	
	/**
	 * This will check if this Setting has a child with the
	 * specified name
	 * @param name The tag to search for
	 * @return
	 */
	public boolean hasChild(String name)
	{
		return (getElement().getChild(name) != null);
	}
	
	/**
	 * This will return the associated XML-Element
	 * @return
	 */
	private Element getElement() 
	{
		return this.element;
	}
}
